package com.liuc.practice.ipersistent.sqlSession.impl;

import com.liuc.practice.ipersistent.config.BoundSql;
import com.liuc.practice.ipersistent.pojo.Configuration;
import com.liuc.practice.ipersistent.pojo.SqlMappedStatement;
import com.liuc.practice.ipersistent.sqlSession.Executor;
import com.liuc.practice.ipersistent.utils.GenericTokenParser;
import com.liuc.practice.ipersistent.utils.ParameterMapping;
import com.liuc.practice.ipersistent.utils.ParameterMappingTokenHandler;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * 执行器的一个实现类，JDBC 封装方法的具体实现
 *
 * @Author Liu Chang
 * @Date 2020/12/13 11:18 上午
 */
public class SampleExecutor implements Executor {
    private Configuration configuration;

    public SampleExecutor(Configuration configuration) {
        this.configuration = configuration;
    }

    /**
     * 原生 JDBC 连接数据库的步骤:
     * 1. 注册驱动
     * 2. 获取数据库连接
     * 3. 获取预编译对象
     * 4. 设置参数
     * 5. 执行 sql 获取结果集
     * 6. 将结果集进行封装成为 Java 对象
     * @param statement
     * @param params
     * @param <E>
     * @return
     * @throws SQLException
     */
    @Override
    public <E> List<E> query(SqlMappedStatement statement, Object... params) throws Exception {
        // 获取连接
        Connection connection = configuration.getDataSource().getConnection();
        // 获取预编译对象
        BoundSql boundSql = getBoundSql(statement.getSql());
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getParsedSql());
        // 设置参数
        if (params != null && params.length > 0) {
            setParams(preparedStatement,
                    statement.getParameterType(),
                    boundSql.getClassAttributeNames(),
                    params);
        }
        // 执行 sql (根据不同的 executeType 执行对应的 preparedStatement 方法)
        switch (statement.getExecuteType()) {
            case "select":
                // 执行 sql 并获取结果集
                ResultSet resultSet = preparedStatement.executeQuery();
                // 将结果集封装为需要的返回值
                return convert2List(statement.getResultType(), resultSet);
            case "insert":
            case "delete":
                preparedStatement.execute();
                return null;
            case "update":
                preparedStatement.executeUpdate();
                return null;
            default:
                throw new Exception("该执行类型无法识别");
        }
    }

    /**
     * e.g.
     * parameterType = "User"
     * User user = new User("1");
     *
     * @param preparedStatement
     * @param parameterType
     * @param params
     * @throws NoSuchFieldException
     */
    private void setParams(PreparedStatement preparedStatement,
                           String parameterType,
                           List<ParameterMapping> classAttributeNames,
                           Object... params) throws NoSuchFieldException,
            ClassNotFoundException,
            IllegalAccessException,
            SQLException {
        
        if (parameterType == null) {
            return;
        }
        // 获取了 parameterType = "User" 的 User 类
        Class<?> parameterTypeClass = Class.forName(parameterType);

        for (int i = 0; i < classAttributeNames.size(); i++) {
            ParameterMapping classAttributeName = classAttributeNames.get(i);

            // id = #{userId}, name = #{userName}
            // parameterAttributeName: "userId", "userName"
            String parameterAttributeName = classAttributeName.getContent();

            // 通过反射获取类属性对象
            // User 类中的属性：declaredField = userId, userName
            Field declaredField = parameterTypeClass.getDeclaredField(parameterAttributeName);
            declaredField.setAccessible(true);
            // params 对应 xxxMapper.xml 中每个 select 标签中的 parameterType 属性
            // parameterType 只有一个，所以封装为查询对象，可以封装多个查询参数
            // 使用可变参数是因为 parameterType 有可能不设置，不设置的情况不用传 null
            // 因此 params 虽然是个可变参，但是实际上只有 params[0]
            Object paramsObject = params[0];
            // 获取指定对象(参数对象paramObject，此处对应 User)中此字段的值
            Object o = declaredField.get(paramsObject);

            // preparedStatement 的下标从 1 开始
            preparedStatement.setObject(i+1, o);
        }
    }

    /**
     * 将获取到的 sql 中的特殊字符进行 #{classAttributeName} 换为 "?"
     *
     * @param sql xml 中定义的 sql 语句
     * @return
     */
    private BoundSql getBoundSql(String sql) {
        // 标记处理类：配置标记解析器来完成对占位符的解析处理工作
        ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler();
        
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", handler);

        // 调用该方法的时候，会将 content(也就是 "classAttributeName") 放入 ParameterMappings 中
        String parsedSql = genericTokenParser.parse(sql);
        List<ParameterMapping> classAttributeNames = handler.getParameterMappings();

        // 所以在这里就要将 classAttributeNames 与 sql 一起封装起来，否则 classAttributeNames 拿到的是空值
        return new BoundSql(parsedSql, classAttributeNames);
    }

    /**
     * 将 resultSet 封装成想要的数据类型
     * @param resultType
     * @param resultSet
     * @param <E>
     * @return
     */
    private <E> List<E> convert2List(String resultType,  ResultSet resultSet) throws ClassNotFoundException,
            SQLException,
            IllegalAccessException,
            InstantiationException,
            IntrospectionException,
            InvocationTargetException {

        // 获取 resultTypeClass
        Class<?> resultTypeClass = Class.forName(resultType);

        List<Object> resultTypeObjects = new ArrayList<>();

        while (resultSet.next()) {
            Object resultObject = resultTypeClass.newInstance();

            // 因为需要知道 resultSet 中有字段有哪些, 元数据中有这些内容
            // 所以需要先获取到 resultSet 的元数据，然后去获取返回结果集具体的字段信息
            ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
            // resultSetMetaData 的字段数组，下标从 1 开始
            for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
                // 数据库中表字段应该与对应类中的属性保持一致，或者按照一定规则对应
                // 简易版中保持一致即可（上面注释中不一致是为了更好的理解，debug 时更加明确）
                String columnName = resultSetMetaData.getColumnName(i);
                Object columnValue = resultSet.getObject(columnName);

                // 内省(Introspector) 是Java语言对JavaBean类属性、事件的处理方法
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                propertyDescriptor.getWriteMethod();
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(resultObject, columnValue);
            }

            resultTypeObjects.add(resultObject);
        }

        return (List<E>) resultTypeObjects;
    }
}
